// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/metrics/public/cpp/ukm_source_id.h"

#include <cmath>
#include <string>

#include "base/atomic_sequence_num.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/rand_util.h"

namespace ukm {

namespace {

const int64_t kLowBitsMask = (INT64_C(1) << 32) - 1;

int64_t GetNumTypeBits() {
  return std::ceil(
      std::log2(static_cast<int64_t>(SourceIdObj::Type::kMaxValue) + 1));
}

}  // namespace

// static
SourceIdObj SourceIdObj::New() {
  // Generate some bits which are unique to this process, so we can generate
  // IDs independently in different processes. IDs generated by this method may
  // collide, but it should be sufficiently rare enough to not impact data
  // quality.
  const static int64_t process_id_bits =
      static_cast<int64_t>(base::RandUint64()) & ~kLowBitsMask;
  // Generate some bits which are unique within the process, using a counter.
  static base::AtomicSequenceNumber seq;
  SourceIdObj local_id =
      FromOtherId(seq.GetNext() + 1, SourceIdObj::Type::DEFAULT);
  // Combine the local and process bits to generate a unique ID.
  return SourceIdObj((local_id.value_ & kLowBitsMask) | process_id_bits);
}

// static
SourceIdObj SourceIdObj::FromOtherId(int64_t other_id, SourceIdObj::Type type) {
  // Note on syntax: std::ceil and std::log2 are not constexpr functions thus
  // these variables cannot be initialized statically in the global scope above.
  // Function static initialization here is thread safe; so they are initialized
  // at most once.
  static const int64_t kNumTypeBits = GetNumTypeBits();
  static const int64_t kTypeMask = (INT64_C(1) << kNumTypeBits) - 1;

  const int64_t type_bits = static_cast<int64_t>(type);
  DCHECK_EQ(type_bits, type_bits & kTypeMask);
  // Stores the type of the source ID in its lower bits, and shift the rest of
  // the ID to make room. This could cause the original ID to overflow, but
  // that should be rare enough that it won't matter for UKM's purposes.
  return SourceIdObj((other_id << kNumTypeBits) | type_bits);
}

SourceIdObj::Type SourceIdObj::GetType() const {
  static const int64_t kNumTypeBits = GetNumTypeBits();
  static const int64_t kTypeMask = (INT64_C(1) << kNumTypeBits) - 1;
  return static_cast<SourceIdObj::Type>(value_ & kTypeMask);
}

SourceId AssignNewSourceId() {
  return ukm::SourceIdObj::New().ToInt64();
}

SourceId ConvertToSourceId(int64_t other_id, SourceIdType id_type) {
  // DCHECK is to restrict the usage of WEBAPK_ID, PAYMENT_APP_ID, and
  // WEB_IDENTITY_ID. These should use the specific
  // |UkmRecorder::GetSourceIdFor*() methods instead.
  // TODO(crbug.com/1046964): Ideally we should restrict
  // SourceIdObj::FromOtherId() as well.
  DCHECK(id_type != SourceIdType::WEBAPK_ID);
  DCHECK(id_type != SourceIdType::PAYMENT_APP_ID);
  DCHECK(id_type != SourceIdType::WEB_IDENTITY_ID);
  return ukm::SourceIdObj::FromOtherId(other_id, id_type).ToInt64();
}

SourceIdType GetSourceIdType(SourceId source_id) {
  return ukm::SourceIdObj::FromInt64(source_id).GetType();
}

std::string GetSourceIdTypeDebugString(SourceId source_id) {
  const auto source_type = GetSourceIdType(source_id);
  switch (source_type) {
    case SourceIdObj::Type::DEFAULT:
      return "DEFAULT";
    case SourceIdObj::Type::NAVIGATION_ID:
      return "NAVIGATION_ID";
    case SourceIdObj::Type::APP_ID:
      return "APP_ID";
    case SourceIdObj::Type::HISTORY_ID:
      return "HISTORY_ID";
    case SourceIdObj::Type::WEBAPK_ID:
      return "WEBAPK_ID";
    case SourceIdObj::Type::PAYMENT_APP_ID:
      return "PAYMENT_APP_ID";
    case SourceIdObj::Type::DESKTOP_WEB_APP_ID:
      return "DESKTOP_WEB_APP_ID";
    case SourceIdObj::Type::WORKER_ID:
      return "WORKER_ID";
    case SourceIdObj::Type::NO_URL_ID:
      return "NO_URL_ID";
    case SourceIdObj::Type::REDIRECT_ID:
      return "REDIRECT_ID";
    case SourceIdObj::Type::WEB_IDENTITY_ID:
      return "WEB_IDENTITY_ID";
    case SourceIdObj::Type::CHROMEOS_WEBSITE_ID:
      return "CHROMEOS_WEBSITE_ID";
    case SourceIdObj::Type::EXTENSION_ID:
      return "EXTENSION_ID";
    case SourceIdObj::Type::SOFT_NAVIGATION_ID:
      return "SOFT_NAVIGATION_ID";
  }
}

SourceId NoURLSourceId() {
  static const SourceId source_id =
      SourceIdObj::FromOtherId(AssignNewSourceId(), SourceIdType::NO_URL_ID)
          .ToInt64();
  return source_id;
}

}  // namespace ukm
